﻿//AcademicPlanner - course registration planning web app.
//Copyright (C) 2009  Boubacar Diallo

//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AcademicPlanner.Data;
using AcademicPlanner.Data.Entities;
using AcademicPlanner.Data.Repositories;
using AcademicPlanner.Domain.Models.Collections;
using AcademicPlanner.Framework.Helpers;
using AcademicPlanner.Framework.Libraries.Validation;
using AcademicPlanner.Domain.Components;

namespace AcademicPlanner.Domain.Models
{
	/// <summary>
	/// Program represents an academic program in the application.
	/// </summary>
	/// <since>1.0</since>
	public class Program : Model
	{
		/// <summary>
		/// Minimum value for the <c>MinimumCredits</c> property of a Program: <c>0</c>.
		/// </summary>
		/// <since>1.0</since>
		public static readonly double MINIMUMCREDITS_MIN_VALUE = 0;

		/// <summary>
		/// The collection of <c>Course</c>s required for this Program.
		/// </summary>
		protected CourseCollection requiredCourses;

		/// <summary>
		/// The collection of <c>Course</c>s recommended for this Program.
		/// </summary>
		protected CourseCollection recommendedCourses;

		/// <summary>
		/// The <c>AcademicPlan</c> this Program belongs to.
		/// </summary>
		/// <since>1.0</since>
		protected AcademicPlan plan;


		/// <summary>
		/// Gets the data layer repository for this Program's underlying data representation.
		/// </summary>
		/// <since>1.0</since>
		protected override EntityRepository EntityRepo { get { return DomainMaster.DSAS.ProgramEntityRepo; } }

		/// <summary>
		/// Gets this Program's underlying data representation.
		/// </summary>
		/// <since>1.0</since>
		protected ProgramEntity Entity { get { return (ProgramEntity)entity; } }

		/// <summary>
		/// Gets this Program's name.
		/// </summary>
		/// <exception cref="T:AcademicPlanner.Domain.NotAuthorizedException">
		/// Thrown with error code <c>DomainErrorCode.M_NOT_AUTHORIZED_VIEW_PROGRAM_NAME</c> 
		/// if the user is not authorized to see this property.
		/// </exception>
		/// <since>1.0</since>
		public string Name
		{
			get
			{
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.VIEW_PROGRAM, this))
					throw new NotAuthorizedException(DomainErrorCode.M_NOT_AUTHORIZED_VIEW_PROGRAM_NAME);
				return Entity.Name;
			}
		}

		/// <summary>
		/// Gets the minimum number of credits required for this Program.
		/// </summary>
		/// <exception cref="T:AcademicPlanner.Domain.NotAuthorizedException">
		/// Thrown with error code <c>DomainErrorCode.M_NOT_AUTHORIZED_VIEW_PROGRAM_MINIMUMCREDITS</c> 
		/// if the user is not authorized to see this property.
		/// </exception>
		/// <since>1.0</since>
		public double MinimumCredits
		{
			get
			{
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.VIEW_PROGRAM, this))
					throw new NotAuthorizedException(DomainErrorCode.M_NOT_AUTHORIZED_VIEW_PROGRAM_MINIMUMCREDITS);
				return Entity.Min_Credits;
			}
		}

		/// <summary>
		/// Gets the <c>AcademicPlan</c> this Program belongs to.
		/// </summary>
		/// <since>1.0</since>
		public AcademicPlan Plan
		{
			get
			{
				SetPlan();
				return plan;
			}
		}

		/// <summary>
		/// Gets the collection of <c>Course</c>s required for this Program.
		/// </summary>
		/// <since>1.0</since>
		public CourseCollection RequiredCourses
		{
			get
			{
				SetRequiredCourses();
				return requiredCourses;
			}
		}

		/// <summary>
		/// Gets the collection of <c>Course</c>s recommended for this Program.
		/// </summary>
		/// <since>1.0</since>
		public CourseCollection RecommendedCourses
		{
			get
			{
				SetRecommendedCourses();
				return recommendedCourses;
			}
		}


		/// <summary>
		/// Constructs a new Program.
		/// </summary>
		/// <remarks>
		/// This constructor invokes the matching super constructor.
		/// </remarks>
		/// <since>1.0</since>
		public Program()
			: base()
		{
			
		}

		/// <summary>
		/// Constructs a new Program with the provided <c>ProgramEntity</c>.
		/// </summary>
		/// <remarks>
		/// This constructor invokes the matching super constructor.
		/// </remarks>
		/// <param name="pEntity">The Program's <c>ProgramEntity</c>.</param>
		/// <since>1.0</since>
		public Program(ProgramEntity pEntity) 
			: base(pEntity)
		{
			
		}


		private void ResetPlan()
		{
			if (Entity.PlanEntity != null)
				plan = DomainMaster.ModelsFountain.GetAcademicPlan(Entity.PlanEntity);
		}

		private void SetPlan()
		{
			if (plan == null)
				ResetPlan();
		}

		private void SetRequiredCourses()
		{
			if (requiredCourses == null)
				ResetRequiredCourses();
		}

		private void ResetRequiredCourses()
		{
			requiredCourses = new CourseCollection(Entity.RequiredCourseEntities);
		}

		private void SetRecommendedCourses()
		{
			if (recommendedCourses == null)
				ResetRecommendedCourses();
		}

		private void ResetRecommendedCourses()
		{
			recommendedCourses = new CourseCollection(Entity.RecommendedCourseEntities);
		}

		/// <summary>
		/// Validates possible values of a Program's properties. <c>null</c> values are ignored.
		/// </summary>
		/// <remarks>
		/// <c>String</c> inputs are sanitized first.
		/// </remarks>
		/// <param name="pName">The academic program's name.</param>
		/// <param name="pMinCredits">The minimum number of credits required for the academic program.</param>
		/// <returns>A Collection of validation errors.</returns>
		/// <seealso cref="M:ValidateName"/>
		/// <seealso cref="M:ValidateMinimumCredits"/>
		/// <seealso cref="M:AcademicPlanner.Framework.Helpers.SecurityHelper.SanitizeInput"/>
		/// <since>1.0</since>
		public static InvalidModelPropertiesErrorCollection Validate(string pName, double ? pMinCredits)
		{
			var rVal = new InvalidModelPropertiesErrorCollection("Program");
			InvalidModelPropertyErrorCode vCode;

			if (pName != null)
			{
				if (!(vCode = ValidateName(SecurityHelper.SanitizeInput(pName)))
						.Equals(InvalidModelPropertyErrorCode.NONE))
					rVal.AddOuter("Name", vCode);
			}
			if (pMinCredits != null)
			{
				if (!(vCode = ValidateMinimumCredits((double)pMinCredits)).Equals(InvalidModelPropertyErrorCode.NONE))
					rVal.AddOuter("MinimumCredits", vCode);
			}

			return rVal;
		}

		/// <summary>
		/// Validates a possible value of a Program's name.
		/// </summary>
		/// <remarks>
		/// The value must not be blank.
		/// </remarks>
		/// <param name="pVal">The value to validate.</param>
		/// <returns>A validation error code.</returns>
		/// <seealso cref="M:AcademicPlanner.Framework.Libraries.Validation.StringValidator.IsNotBlank"/>
		/// <since>1.0</since>
		public static InvalidModelPropertyErrorCode ValidateName(string pVal)
		{
			var rVal = InvalidModelPropertyErrorCode.NONE;

			if (!StringValidator.IsNotBlank(pVal))
				rVal = InvalidModelPropertyErrorCode.REQUIRED;

			return rVal;
		}

		/// <summary>
		/// Validates a possible value of minimum credits required for a Program.
		/// </summary>
		/// <remarks>
		/// The value must he greater than <see cref="P:MINIMUMCREDITS_MIN_VALUE"/>.
		/// </remarks>
		/// <param name="pVal">The value to validate.</param>
		/// <returns>A validation error code.</returns>
		/// <since>1.0</since>
		public static InvalidModelPropertyErrorCode ValidateMinimumCredits(double pVal)
		{
			var rVal = InvalidModelPropertyErrorCode.NONE;

			if (pVal <= MINIMUMCREDITS_MIN_VALUE)
				rVal = InvalidModelPropertyErrorCode.TOO_SMALL;

			return rVal;
		}

		internal void RemoveRequiredCourse(Course pCourse)
		{
			var vEnt = (CourseEntity)GetOtherModelEntity(pCourse);

			if (Entity.RequiredCourseEntities.Contains(vEnt))
				Entity.RequiredCourseEntities.Remove(vEnt);
		}

		internal void AddRequiredCourse(Course pCourse)
		{
			var vEnt = (CourseEntity)GetOtherModelEntity(pCourse);

			if (!Entity.RequiredCourseEntities.Contains(vEnt))
				Entity.RequiredCourseEntities.Add(vEnt);
		}

		internal void RemoveRecommendedCourse(Course pCourse)
		{
			var vEnt = (CourseEntity)GetOtherModelEntity(pCourse);

			if (Entity.RecommendedCourseEntities.Contains(vEnt))
				Entity.RecommendedCourseEntities.Remove(vEnt);
		}

		internal void AddRecommendedCourse(Course pCourse)
		{
			var vEnt = (CourseEntity)GetOtherModelEntity(pCourse);

			if (!Entity.RecommendedCourseEntities.Contains(vEnt)
				&& !Entity.RequiredCourseEntities.Contains(vEnt))
				Entity.RecommendedCourseEntities.Add(vEnt);
		}

		/// <summary>
		/// Checks whether a <c>Course</c> is recommended
		/// for this Program.
		/// </summary>
		/// <param name="pCourse">The <c>Course</c> to check.</param>
		/// <returns>Whether the <c>Course</c> is recommended.</returns>
		/// <since>1.0</since>
		public bool IsCourseRecommended(Course pCourse)
		{
			SetRecommendedCourses();
			return recommendedCourses.ToList().Contains(pCourse);
		}

		/// <summary>
		/// Checks whether a <c>Course</c> is required
		/// for this Program.
		/// </summary>
		/// <param name="pCourse">The <c>Course</c> to check.</param>
		/// <returns>Whether the <c>Course</c> is required.</returns>
		/// <since>1.0</since>
		public bool IsCourseRequired(Course pCourse)
		{
			SetRequiredCourses();
			return requiredCourses.ToList().Contains(pCourse);
		}

		internal void Set(string pName, double ? pMinCredits)
		{
			var vErrs = Validate(pName, pMinCredits);

			if (vErrs.OuterErrors.Count > 0)
				throw new InvalidModelPropertiesException(DomainErrorCode.M_INVALID_PROGRAM_PROPERTIES, vErrs);

			SetPlan();
			if (plan != null)
			{
				vErrs = plan.ValidateProgram(entity.Id, pName ?? "");
				if (vErrs.OuterErrors.Count > 0)
					throw new InvalidModelPropertiesException(DomainErrorCode.M_INVALID_PROGRAM_PROPERTIES, vErrs);
			}

			if (pName != null)
				Entity.Name = SecurityHelper.SanitizeInput(pName);
			if (pMinCredits != null)
				Entity.Min_Credits = (double)pMinCredits;
		}
	}
}
